<template>
  <div class="dispatch-logic-page">
    <el-row :gutter="20" class="overview-row">
      <el-col :xs="24" :lg="18">
        <el-card class="summary-card" shadow="never" :body-style="{ padding: '14px 18px 12px' }">
          <div class="card-header">
            <div class="card-title">调度状态概览</div>
            <div class="card-actions">
              <el-switch
                v-model="dispatchEnabled"
                :loading="switchLoading"
                inline-prompt
                active-text="开启"
                inactive-text="关闭"
                @change="handleSwitchChange"
              />
              <span class="update-time" v-if="lastUpdated">上次更新：{{ formatDateTime(lastUpdated) }}</span>
              <el-button type="primary" size="small" :loading="loading" @click="fetchData">刷新</el-button>
            </div>
          </div>
          <div class="summary-metrics combined-overview">
            <div class="metrics-section">
              <div class="section-title">功率指标</div>
              <div class="metric-grid four-columns">
                <div v-for="item in evaluationMetrics" :key="`eval-${item.label}`" class="metric-card condensed">
                  <div class="metric-label">{{ item.label }}</div>
                  <div class="metric-value">{{ item.value }}</div>
                </div>
              </div>
            </div>
            
            <div class="metrics-section">
              <div class="section-title">电价指标</div>
              <div class="metric-grid three-columns">
                <div v-for="item in priceMetrics" :key="`price-${item.label}`" 
                     class="metric-card condensed" 
                     :class="{ 'highlight': item.highlight }">
                  <div class="metric-label">{{ item.label }}</div>
                  <div class="metric-value">{{ item.value }}</div>
                </div>
              </div>
            </div>
            
            <div class="metrics-section">
              <div class="section-title">运行参数</div>
              <div class="metric-grid six-columns">
                <div v-for="item in inputParameters" :key="`input-${item.label}`" class="metric-card condensed">
                  <div class="metric-label">{{ item.label }}</div>
                  <div class="metric-value">{{ item.value }}</div>
                </div>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>

      <el-col :xs="24" :lg="6">
        <el-card class="summary-card" shadow="never" :body-style="{ padding: '14px 18px 12px' }">
          <div class="card-header">
            <div class="card-title">输出动作</div>
          </div>
          <div class="actions-body">
            <div v-if="actions.length" class="actions-content">
              <div class="actions-tags">
                <div
                  v-for="item in actions"
                  :key="item.code"
                  class="action-chip"
                  :class="[`is-${item.tagType}`]"
                >
                  <span class="chip-label">{{ item.label }}</span>
                  <span v-if="actionDescriptions[item.code]" class="chip-desc">{{ actionDescriptions[item.code] }}</span>
                </div>
              </div>
              <div class="actions-note">执行策略将同步至储能与用能控制模块</div>
              <div class="storage-command" v-if="storageCommandLabel">
                储能执行指令：<span class="storage-command__value">{{ storageCommandLabel }}</span>
              </div>
            </div>
            <el-empty v-else description="暂无调度动作" :image-size="80" />
          </div>
        </el-card>
      </el-col>
    </el-row>

    <div class="logic-section">
      <el-card class="tree-card" shadow="never" :body-style="{ padding: '12px 18px 14px' }">
        <template #header>
          <div class="tree-header">
            <div class="tree-header-main">
              <div class="tree-title">
                <el-icon><DataLine /></el-icon>
                <span class="title-text">调度决策图</span>
                <span class="tree-badge">实时</span>
              </div>
              <div class="tree-caption">展示当前判定路径，可拖拽缩放查看细节</div>
            </div>
            <div class="branch-hint">
              <strong>提示</strong>：上分支=条件成立，下分支=条件不成立
            </div>
          </div>
        </template>
        <div v-if="traceSteps.length" class="logic-steps">
          <el-steps
            :active="activeStepIndex"
            finish-status="success"
            align-center
          >
            <el-step
              v-for="step in traceSteps"
              :key="step.key"
              :title="step.title"
              :description="describeStep(step)"
            />
          </el-steps>
        </div>
        <div class="logic-chart-wrapper">
          <div v-if="!dispatchSnapshot" class="chart-empty">
            <el-empty description="暂无调度数据" :image-size="80" />
          </div>
          <div v-else ref="chartRef" class="logic-chart"></div>
        </div>
      </el-card>
    </div>
  </div>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, onActivated, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { DataLine } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { dispatchAPI, handleApiError } from '@/utils/api'

const loading = ref(false)
const dispatchSnapshot = ref(null)
const lastUpdated = ref(null)
const dispatchEnabled = ref(true)
const switchLoading = ref(false)
let suppressSwitchEvent = false

const chartRef = ref(null)
let chartInstance = null

const actionLabels = {
  SelfUseAllPv: '光伏全额自用',
  StoreAllPv: '光伏全额充电',
  StorePartPv: '光伏部分充电',
  FeedExcessPv: '余电上网',
  GridSupplyLoad: '市电补足负载',
  StorageDischargeFull: '储能全力放电',
  StorageDischargeControl: '储能按需放电',
  PauseStorageCharge: '暂停储能充电',
  PauseStorageDischarge: '暂停储能放电',
  IdleAll: '维持当前状态'
}

const actionTagTypes = {
  SelfUseAllPv: 'primary',
  StoreAllPv: 'warning',
  StorePartPv: 'warning',
  FeedExcessPv: 'success',
  GridSupplyLoad: 'info',
  StorageDischargeFull: 'danger',
  StorageDischargeControl: 'danger',
  PauseStorageCharge: 'info',
  PauseStorageDischarge: 'info',
  IdleAll: 'info'
}

const actionDescriptions = {
  SelfUseAllPv: '光伏电量全部供应现场负载使用',
  StoreAllPv: '优先充满储能，备用或延后利用',
  StorePartPv: '按限制充能，剩余功率协同负载与电网',
  FeedExcessPv: '结余功率回送电网获得收益',
  GridSupplyLoad: '由市电补足当前负载缺口',
  StorageDischargeFull: '储能全功率输出支撑负载',
  StorageDischargeControl: '储能按目标功率平稳放电',
  PauseStorageCharge: '高价或峰时暂停充电',
  PauseStorageDischarge: '暂缓放电，保留可用电量',
  IdleAll: '保持当前波动策略不做调整'
}

const windowLabels = {
  ChargeWindow: '充电窗口',
  DischargeWindow: '放电窗口',
  IdleWindow: '禁调窗口'
}

const stateLabels = {
  Charging: '充电中',
  Discharging: '放电中',
  Idle: '空闲'
}

const makeNode = (id, label, children = [], options = {}) => {
  const node = { id, label }
  if (children.length) {
    node.children = children
  }
  if (options.description) {
    node.description = options.description
  }
  if (options.traceId) {
    node.traceId = options.traceId
  }
  if (options.requires) {
    node.requires = options.requires
  }
  return node
}

const createChargeNodes = (suffix, variants, requires = [], labelOptions = {}) => {
  const nodes = []
  if (variants.includes('all')) {
    nodes.push(makeNode(`charge_capacity_all_${suffix}`, labelOptions.all || '储能功率≥光伏功率 → 全部存储', [], {
      traceId: 'charge_capacity:all',
      description: '满足储能功率≥光伏功率，全部将光伏电量存入储能',
      requires
    }))
  }
  if (variants.includes('partial')) {
    nodes.push(makeNode(`charge_capacity_partial_${suffix}`, labelOptions.partial || '储能功率≤光伏功率 → 部分存储，部分自用', [], {
      traceId: 'charge_capacity:partial',
      description: '储能功率不足以覆盖光伏功率，剩余电量直接供现场负载',
      requires
    }))
  }
  if (variants.includes('partial_cover')) {
    nodes.push(makeNode(`charge_capacity_partial_cover_${suffix}`, labelOptions.partialCover || '负载功率+储能功率≥光伏功率 → 优先存储，剩余自用', [], {
      traceId: 'charge_capacity:partial_cover',
      description: '光伏与负载功率基本持平，优先满足储能，其余供现场使用',
      requires
    }))
  }
  if (variants.includes('excess')) {
    nodes.push(makeNode(`charge_capacity_excess_${suffix}`, labelOptions.excess || '负载功率+储能功率≤光伏功率 → 优先存储，其次自用，余电上网', [], {
      traceId: 'charge_capacity:excess',
      description: '充能后仍有富余光伏电量，继续自用或上网售电',
      requires
    }))
  }
  if (variants.includes('pause')) {
    nodes.push(makeNode(`charge_pause_${suffix}`, labelOptions.pause || '高峰电价 → 暂停储能充电，优先自用/上网', [], {
      traceId: 'charge_control:pause_charge',
      description: '高电价时段暂停充电，避免高成本的一次充电',
      requires
    }))
  }
  return nodes
}

const createDischargeNodes = (suffix, options = {}, requires = []) => {
  const nodes = []
  const mode = options.mode || 'storageOnly' // storageOnly | combined

  const partialCondition =
    mode === 'combined'
      ? '储能功率+光伏功率≤负载功率'
      : '储能功率≤负载功率'
  const enoughCondition =
    mode === 'combined'
      ? '储能功率+光伏功率≥负载功率'
      : '储能功率≥负载功率'

  const labels = options.labels || {}
  const partialAction =
    labels.partial ||
    (mode === 'combined' ? '全部自用，全力放电' : '全部上网，全力放电')
  const enoughAction =
    labels.enough ||
    (mode === 'combined' ? '继续判断放电策略' : '全部上网，控制放电')

  const costLabels = options.costLabels || {}
  const costHigherAction =
    costLabels.higher || '全部自用，控制放电'
  const costLowerAction =
    costLabels.lower || '部分自用，部分上网，全力放电'
  const costOrder = options.order || 'capacityFirst' // capacityFirst | costFirst

  if (options.includeCost) {
    if (costOrder === 'capacityFirst') {
      nodes.push(makeNode(`discharge_partial_${suffix}`, `${partialCondition} → ${partialAction}`, [], {
        traceId: 'storage_discharge_capacity:partial',
        requires
      }))

      const costChildren = [
        makeNode(`cost_high_${suffix}`, `储能已充电价≥光伏售出价 → ${costHigherAction}`, [], {
          traceId: 'storage_cost_vs_pv:higher',
          requires: [...requires, 'storage_discharge_capacity:enough']
        }),
        makeNode(`cost_low_${suffix}`, `储能已充电价<光伏售出价 → ${costLowerAction}`, [], {
          traceId: 'storage_cost_vs_pv:lower',
          requires: [...requires, 'storage_discharge_capacity:enough']
        })
      ]

      nodes.push(makeNode(`discharge_enough_${suffix}`, enoughCondition, costChildren, {
        traceId: 'storage_discharge_capacity:enough',
        description: labels.enoughDescription,
        requires
      }))
    } else {
      nodes.push(makeNode(`cost_high_${suffix}`, `储能已充电价≥光伏售出价 → ${costHigherAction}`, [], {
        traceId: 'storage_cost_vs_pv:higher',
        requires
      }))

      const lowerChildren = createDischargeNodes(
        `${suffix}_lower_capacity`,
        { capacityOnly: true, mode, labels },
        [...requires, 'storage_cost_vs_pv:lower']
      )

      nodes.push(makeNode(`cost_low_${suffix}`, costLabels.lowerNodeLabel || '储能已充电价<光伏售出价', lowerChildren, {
        traceId: 'storage_cost_vs_pv:lower',
        description: costLabels.lowerDescription || '根据储能可用功率确定放电策略',
        requires
      }))
    }
  } else if (options.capacityOnly) {
    nodes.push(makeNode(`discharge_enough_${suffix}`, `${enoughCondition} → ${enoughAction}`, [], {
      traceId: 'storage_discharge_capacity:enough',
      requires
    }))
    nodes.push(makeNode(`discharge_partial_${suffix}`, `${partialCondition} → ${partialAction}`, [], {
      traceId: 'storage_discharge_capacity:partial',
      requires
    }))
  } else {
    nodes.push(makeNode(`discharge_enough_${suffix}`, `${enoughCondition} → ${enoughAction}`, [], {
      traceId: 'storage_discharge_capacity:enough',
      requires
    }))
    nodes.push(makeNode(`discharge_partial_${suffix}`, `${partialCondition} → ${partialAction}`, [], {
      traceId: 'storage_discharge_capacity:partial',
      requires
    }))
  }
  return nodes
}

const createStorageWindowNode = (suffix, options) => {
  const baseRequires = options.requires || []
  const chargeNodes = createChargeNodes(
    `${suffix}_charge`,
    options.chargeVariants || [],
    [...baseRequires, 'storage_window:charge'],
    options.chargeLabels || {}
  )
  const dischargeNodes = createDischargeNodes(
    `${suffix}_discharge`,
    options.dischargeOptions || {},
    [...baseRequires, 'storage_window:discharge']
  )
  const idleChildren = (options.idleChildren || []).map((child, index) =>
    makeNode(`${child.id || 'idle_child'}_${suffix}_${index}`, child.label, child.children || [], {
      traceId: child.traceId,
      description: child.description,
      requires: [...(child.requires || []), ...baseRequires, 'storage_window:idle']
    })
  )

  return makeNode(
    `storage_window_${suffix}`,
    options.label,
    [
      makeNode(`storage_window_charge_${suffix}`, '当前储能处于充电时段', chargeNodes, {
        traceId: 'storage_window:charge',
        requires: baseRequires
      }),
      makeNode(`storage_window_discharge_${suffix}`, '当前储能处于放电时段', dischargeNodes, {
        traceId: 'storage_window:discharge',
        requires: baseRequires
      }),
      makeNode(`storage_window_idle_${suffix}`, options.idleLabel || '当前储能处于空闲窗口', idleChildren, {
        traceId: 'storage_window:idle',
        description: options.idleDescription,
        requires: baseRequires
      })
    ],
    { traceId: 'storage_window', requires: baseRequires }
  )
}

const createPeakNode = (suffix, trueChildren, falseChildren, requires = []) => makeNode(
  `is_peak_period_${suffix}`,
  '当前市电价是否为最高电价？',
  [
    makeNode(`is_peak_period_true_${suffix}`, '是（当前市电价为最高电价）', trueChildren, {
      traceId: 'is_peak_period:true',
      requires
    }),
    makeNode(`is_peak_period_false_${suffix}`, '否（当前市电价不是最高电价）', falseChildren, {
      traceId: 'is_peak_period:false',
      requires
    })
  ],
  { traceId: 'is_peak_period', requires }
)

const createGridPeakNode = (suffix, higherChildren, lowerChildren, traceKey = 'grid_peak_vs_pv', requires = []) => makeNode(
  `grid_peak_vs_pv_${suffix}`,
  '最高电价与光伏发电售出价比较',
  [
    makeNode(`grid_peak_ge_${suffix}`, '最高电价≥光伏售电价（峰价占优）', higherChildren, {
      traceId: `${traceKey}:higher_or_equal`,
      requires
    }),
    makeNode(`grid_peak_lt_${suffix}`, '最高电价<光伏售电价（峰价不占优）', lowerChildren, {
      traceId: `${traceKey}:lower`,
      requires
    })
  ],
  { traceId: traceKey, requires }
)

const createGridPeakAltNode = (suffix, lowerChildren, higherChildren, requires = []) => makeNode(
  `grid_peak_vs_pv_alt_${suffix}`,
  '最高电价与光伏发电售出价比较',
  [
    makeNode(`grid_peak_le_${suffix}`, '最高电价≤光伏售电价（峰价不占优）', lowerChildren, {
      traceId: 'grid_peak_vs_pv:lower_or_equal',
      requires
    }),
    makeNode(`grid_peak_gt_${suffix}`, '最高电价>光伏售电价（峰价占优）', higherChildren, {
      traceId: 'grid_peak_vs_pv:higher',
      requires
    })
  ],
  { traceId: 'grid_peak_vs_pv', requires }
)

const loadGapBranch = makeNode('load_ge_pv_true_branch', '负载功率大于发电功率（缺口场景）', [
  makeNode('grid_price_compare_gap', '市电价是否大于等于光伏发电售出价？', [
    makeNode('grid_price_high_gap', '市电价≥光伏售电价（购电更贵）', [
      createPeakNode('gap_high',
        [
          createStorageWindowNode('gap_high_peak', {
            label: '当前储能运行状态（市电价为最高电价）',
            requires: ['load_ge_pv:true', 'grid_ge_pv_price:true', 'is_peak_period:true'],
            chargeVariants: ['pause'],
            chargeLabels: { pause: '全部自用，暂停充电' },
            dischargeOptions: {
              includeCost: true,
              mode: 'combined',
              order: 'capacityFirst',
              labels: { partial: '全部自用，全力放电' },
              costLabels: {
                higher: '全部自用，控制放电',
                lower: '部分自用，部分上网，全力放电'
              }
            },
            idleLabel: '储能空闲 → 光伏自用+市电补足',
            idleDescription: '高峰期仍需市电补足缺口'
          })
        ],
        [
          createStorageWindowNode('gap_high_offpeak', {
            label: '当前储能运行状态（市电价不是最高电价）',
            requires: ['load_ge_pv:true', 'grid_ge_pv_price:true', 'is_peak_period:false'],
            chargeVariants: ['all', 'partial'],
            dischargeOptions: {
              includeCost: true,
              mode: 'combined',
              order: 'capacityFirst',
              labels: { partial: '全部自用，全力放电' },
              costLabels: {
                higher: '全部自用，控制放电',
                lower: '部分自用，部分上网，全力放电'
              }
            },
            idleLabel: '储能空闲 → 光伏自用+市电补足'
          })
        ],
        ['load_ge_pv:true', 'grid_ge_pv_price:true']
      )
    ], {
      traceId: 'grid_ge_pv_price:true'
    }),
    makeNode('grid_price_low_gap', '市电价≤光伏售电价（购电便宜）', [
      createPeakNode('gap_low',
        [
          createStorageWindowNode('gap_low_peak', {
            label: '当前储能运行状态（市电价为最高电价）',
            requires: ['load_ge_pv:true', 'grid_ge_pv_price:false', 'is_peak_period:true'],
            chargeVariants: ['pause'],
            chargeLabels: { pause: '全部上网，暂停充电（储能不变）' },
            dischargeOptions: {
              capacityOnly: true,
              mode: 'storageOnly',
              labels: {
                partial: '全部上网，全力放电',
                enough: '全部上网，控制放电'
              }
            },
            idleLabel: '储能空闲 → 余电上网'
          })
        ],
        [
          createGridPeakNode('gap_low_offpeak',
            [
              createStorageWindowNode('gap_low_offpeak_high', {
                label: '当前储能运行状态（最高电价≥光伏售电价）',
                requires: ['load_ge_pv:true', 'grid_ge_pv_price:false', 'is_peak_period:false', 'grid_peak_vs_pv:higher_or_equal'],
                chargeVariants: ['pause'],
                chargeLabels: { pause: '全部上网，储能不变' },
                dischargeOptions: {
                  capacityOnly: true,
                  mode: 'storageOnly',
                  labels: {
                    partial: '全部上网，全力放电',
                    enough: '全部上网，控制放电'
                  }
                },
                idleLabel: '储能空闲 → 优先上网'
              })
            ],
            [
              createStorageWindowNode('gap_low_offpeak_low', {
                label: '当前储能运行状态（最高电价<光伏售电价）',
                requires: ['load_ge_pv:true', 'grid_ge_pv_price:false', 'is_peak_period:false', 'grid_peak_vs_pv:lower'],
                chargeVariants: ['pause'],
                chargeLabels: { pause: '全部上网，储能不变' },
                dischargeOptions: {
                  capacityOnly: true,
                  mode: 'storageOnly',
                  labels: {
                    partial: '全部上网，全力放电',
                    enough: '全部上网，控制放电'
                  }
                },
                idleLabel: '储能空闲 → 直接上网'
              })
            ],
            'grid_peak_vs_pv',
            ['load_ge_pv:true', 'grid_ge_pv_price:false', 'is_peak_period:false']
          )
        ],
        ['load_ge_pv:true', 'grid_ge_pv_price:false']
      )
    ], {
      traceId: 'grid_ge_pv_price:false'
    })
  ], { traceId: 'grid_ge_pv_price' })
], { traceId: 'load_ge_pv:true' })

// 余电（负载<光伏）分支
const loadSurplusBranch = makeNode('load_ge_pv_false_branch', '负载需求量小于发电量（余电场景）', [
  makeNode('grid_price_compare_surplus', '市电价是否大于等于光伏发电售出价？', [
    makeNode('grid_price_high_surplus', '市电价≥光伏售电价（购电更贵）', [
      createPeakNode('surplus_high',
        [
          createStorageWindowNode('surplus_high_peak', {
            label: '当前储能运行状态（市电价为最高电价）',
            requires: ['load_ge_pv:false', 'grid_ge_pv_price:true', 'is_peak_period:true'],
            chargeVariants: ['partial_cover', 'excess'],
            chargeLabels: {
              partialCover: '负载功率+储能功率≥光伏功率 → 优先自用，控制存储',
              excess: '负载功率+储能功率≤光伏功率 → 优先自用，其次存储，余电上网'
            },
            dischargeOptions: {
              includeCost: true,
              mode: 'storageOnly',
              order: 'costFirst',
              labels: {
                partial: '全力放电，优先自用，余电上网',
                enough: '控制放电，全部上网'
              },
              costLabels: {
                higher: '暂停放电、优先自用，余电上网',
                lowerNodeLabel: '储能已充电价<光伏售出价（继续判断储能功率）',
                lowerDescription: '根据储能功率判定控制或全力放电'
              }
            },
            idleLabel: '储能空闲 → 光伏自用+上网'
          })
        ],
        [
          createStorageWindowNode('surplus_high_offpeak', {
            label: '当前储能运行状态（市电价不是最高电价）',
            requires: ['load_ge_pv:false', 'grid_ge_pv_price:true', 'is_peak_period:false'],
            chargeVariants: ['all', 'partial_cover', 'excess'],
            dischargeOptions: {
              includeCost: true,
              mode: 'storageOnly',
              order: 'costFirst',
              labels: {
                partial: '全力放电，优先自用，余电上网',
                enough: '控制放电，全部上网'
              },
              costLabels: {
                higher: '暂停放电、优先自用，余电上网',
                lowerNodeLabel: '储能已充电价<光伏售出价（继续判断储能功率）',
                lowerDescription: '根据储能功率判定控制或全力放电'
              }
            },
            idleLabel: '储能空闲 → 光伏自用+上网'
          })
        ],
        ['load_ge_pv:false', 'grid_ge_pv_price:true']
      )
    ], {
      traceId: 'grid_ge_pv_price:true'
    }),
    makeNode('grid_price_low_surplus', '市电价≤光伏售电价（购电便宜）', [
      createPeakNode('surplus_low',
        [
          createStorageWindowNode('surplus_low_peak', {
            label: '当前储能运行状态（市电价为最高电价）',
            requires: ['load_ge_pv:false', 'grid_ge_pv_price:false', 'is_peak_period:true'],
            chargeVariants: ['pause'],
            chargeLabels: { pause: '全部上网，暂停充电' },
            dischargeOptions: {
              capacityOnly: true,
              mode: 'storageOnly',
              labels: {
                partial: '全部上网，全力放电',
                enough: '全部上网，控制放电'
              }
            },
            idleLabel: '储能空闲 → 上网优先'
          })
        ],
        [
          createGridPeakAltNode('surplus_low_offpeak',
            [
              createStorageWindowNode('surplus_low_offpeak_lower', {
                label: '当前储能运行状态（最高电价≤光伏售电价）',
                requires: ['load_ge_pv:false', 'grid_ge_pv_price:false', 'is_peak_period:false', 'grid_peak_vs_pv:lower_or_equal'],
                chargeVariants: ['pause'],
                chargeLabels: { pause: '全部上网，储能不变' },
                dischargeOptions: {
                  capacityOnly: true,
                  mode: 'storageOnly',
                  labels: {
                    partial: '全部上网，全力放电',
                    enough: '全部上网，控制放电'
                  }
                },
                idleLabel: '储能空闲 → 余电上网'
              })
            ],
            [
              createStorageWindowNode('surplus_low_offpeak_higher', {
                label: '当前储能运行状态（最高电价>光伏售电价）',
                requires: ['load_ge_pv:false', 'grid_ge_pv_price:false', 'is_peak_period:false', 'grid_peak_vs_pv:higher'],
                chargeVariants: ['all', 'partial'],
                chargeLabels: {
                  all: '储能功率≥光伏功率 → 全部存储',
                  partial: '储能功率<光伏功率 → 优先存储，余电上网'
                },
                dischargeOptions: {
                  capacityOnly: true,
                  mode: 'storageOnly',
                  labels: {
                    partial: '全部上网，全力放电',
                    enough: '全部上网，控制放电'
                  }
                },
                idleLabel: '储能空闲 → 余电上网'
              })
            ],
            'grid_peak_vs_pv',
            ['load_ge_pv:false', 'grid_ge_pv_price:false', 'is_peak_period:false']
          )
        ],
        ['load_ge_pv:false', 'grid_ge_pv_price:false']
      )
    ], {
      traceId: 'grid_ge_pv_price:false'
    })
  ], { traceId: 'grid_ge_pv_price' })
], { traceId: 'load_ge_pv:false' })

// 根节点：负载功率 vs 光伏功率
const logicTree = [
  makeNode('load_ge_pv_root', '负载功率是否大于发电功率？', [loadGapBranch, loadSurplusBranch], {
    traceId: 'load_ge_pv'
  })
]

const traceNodeMap = new Map()

const registerTraceNode = (node) => {
  if (!node || typeof node !== 'object') {
    return
  }
  if (node.traceId) {
    traceNodeMap.set(node.traceId, node)
  }
  ;(node.children || []).forEach(registerTraceNode)
}

logicTree.forEach(registerTraceNode)

const isDecisionOutcomeNode = (node) => {
  if (!node || typeof node !== 'object') {
    return false
  }
  const trace = node.traceId
  if (!trace || typeof trace !== 'string') {
    return false
  }
  const [, outcome] = trace.split(':')
  return outcome === 'true' || outcome === 'false'
}

function collapseDecisionOutcomeNode(node) {
  if (!node) {
    return null
  }
  const originalChildren = Array.isArray(node.children) ? node.children : []
  const processedChildren = []

  originalChildren.forEach((child) => {
    if (isDecisionOutcomeNode(child)) {
      const collapsed = collapseDecisionOutcomeNodes(child.children || [])
      collapsed.forEach((grandChild) => {
        if (grandChild) {
          processedChildren.push(grandChild)
        }
      })
    } else {
      const processed = collapseDecisionOutcomeNode(child)
      if (processed) {
        processedChildren.push(processed)
      }
    }
  })

  const cloned = { ...node }
  if (processedChildren.length) {
    cloned.children = processedChildren
  } else {
    delete cloned.children
  }
  return cloned
}

function collapseDecisionOutcomeNodes(nodes = []) {
  return nodes
    .map((node) => collapseDecisionOutcomeNode(node))
    .filter((item) => Boolean(item))
}

const handleResize = () => {
  if (chartInstance) {
    chartInstance.resize()
  }
}

const disposeChart = () => {
  if (chartInstance) {
    window.removeEventListener('resize', handleResize)
    chartInstance.dispose()
    chartInstance = null
  }
}

const buildChartData = () => {
  const convertNode = (node) => {
    const children = (node.children || []).map(convertNode)
    const active = isNodeActive(node)
    const nodeData = {
      name: node.label,
      value: node.description || '',
      children
    }

    const lineColor = active ? '#409eff' : '#c0c4cc'
    nodeData.label = {
      color: active ? '#409eff' : '#606266',
      fontSize: 12,
      fontWeight: active ? 600 : 400
    }
    nodeData.lineStyle = {
      color: lineColor,
      width: active ? 2 : 1.2
    }
    nodeData.itemStyle = {
      color: active ? '#409eff' : '#ffffff',
      borderColor: lineColor,
      borderWidth: active ? 2 : 1.2
    }
    nodeData.symbol = 'circle'
    nodeData.symbolSize = active ? 16 : 10
    return nodeData
  }

  const collapsedTree = collapseDecisionOutcomeNodes(logicTree)
  return collapsedTree.map(convertNode)
}

const buildChartOption = () => ({
  tooltip: {
    trigger: 'item',
    triggerOn: 'mousemove',
    formatter: (params) => {
      if (params.value) {
        return `${params.name}<br/>${params.value}`
      }
      return params.name
    }
  },
  series: [
    {
      type: 'tree',
      data: buildChartData(),
      layout: 'orthogonal',
      orient: 'LR',
      top: '4%',
      left: '4%',
      bottom: '4%',
      right: '4%',
      symbol: 'circle',
      symbolSize: 12,
      nodePadding: 280,
      edgeShape: 'curve',
      edgeForkPosition: '55%',
      expandAndCollapse: false,
      initialTreeDepth: -1,
      animationDuration: 300,
      animationDurationUpdate: 300,
      roam: true,
      lineStyle: {
        color: '#c0c4cc',
        width: 1
      },
      labelLayout: {
        hideOverlap: false,
        moveOverlap: 'shiftY',
        draggable: false
      },
      label: {
        position: 'bottom',
        verticalAlign: 'top',
        align: 'center',
        distance: 8,
        fontSize: 12,
        lineHeight: 18,
        width: 180,
        overflow: 'breakAll',
        padding: [2, 4, 0, 4],
        color: '#2563eb',
        fontWeight: 600
      },
      leaves: {
        label: {
          position: 'right',
          align: 'left',
          verticalAlign: 'middle',
          distance: 8,
          lineHeight: 18,
          width: 220,
          overflow: 'breakAll',
          color: '#4b5563',
          fontWeight: 500
        }
      },
      levels: [
        {
          itemStyle: { borderWidth: 1.2 },
          symbolSize: 13,
          lineStyle: { width: 1.2 },
          label: { fontSize: 13, lineHeight: 20, width: 200, overflow: 'breakAll' }
        },
        {
          itemStyle: { borderWidth: 1 },
          symbolSize: 12,
          lineStyle: { width: 1 },
          label: { fontSize: 12, lineHeight: 18, width: 200, overflow: 'breakAll' }
        },
        {
          itemStyle: { borderWidth: 1 },
          symbolSize: 11,
          lineStyle: { width: 0.9 },
          label: { fontSize: 12, lineHeight: 18, width: 200, overflow: 'breakAll' }
        }
      ]
    }
  ]
})

const ensureChartInstance = async () => {
  if (!chartRef.value || chartInstance) {
    return
  }
  await nextTick()
  if (chartRef.value && !chartInstance) {
    chartInstance = echarts.init(chartRef.value, null, { renderer: 'svg' })
    window.addEventListener('resize', handleResize)
  }
}

const renderChart = async () => {
  if (!dispatchSnapshot.value) {
    disposeChart()
    return
  }
  await ensureChartInstance()
  if (!chartInstance) {
    return
  }
  chartInstance.setOption(buildChartOption(), true)
  chartInstance.resize()
}

const activeTraceIds = computed(() => {
  const set = new Set()
  const trace = dispatchSnapshot.value?.trace || []
  trace.forEach((entry) => {
    const [key, value] = entry.split(':')
    if (key) {
      set.add(key)
    }
    if (value !== undefined) {
      set.add(`${key}:${value}`)
    }
  })
  return set
})

const isNodeActive = (node) => {
  if (!node.traceId) {
    return false
  }
  if (!activeTraceIds.value.has(node.traceId)) {
    return false
  }
  if (node.requires) {
    if (Array.isArray(node.requires)) {
      if (!node.requires.every((req) => activeTraceIds.value.has(req))) {
        return false
      }
    } else {
      // 不符合数组格式时视为条件未满足，避免 runtime 错误
      return false
    }
  }
  return true
}

watch(() => dispatchSnapshot.value, async (snapshot) => {
  if (snapshot) {
    await renderChart()
  } else {
    disposeChart()
  }
})

watch(() => Array.from(activeTraceIds.value), () => {
  if (dispatchSnapshot.value) {
    renderChart()
  }
})

const actions = computed(() => {
  const list = dispatchSnapshot.value?.actions || []
  return list.map((code) => ({
    code,
    label: actionLabels[code] || code,
    tagType: actionTagTypes[code] || 'info'
  }))
})

const storageCommandLabel = computed(() => {
  const command = dispatchSnapshot.value?.storage_device_command
  switch (command) {
    case 'Start':
      return '启动储能运行'
    case 'Stop':
      return '停止储能运行'
    case 'None':
      return '不下发控制'
    default:
      return command || ''
  }
})

const formatNumber = (value) => {
  if (value === null || value === undefined || Number.isNaN(value)) {
    return '--'
  }
  if (Math.abs(value) >= 100) {
    return Number(value).toFixed(0)
  }
  return Number(value).toFixed(2)
}

const formatBoolean = (value) => (value ? '是' : '否')

const formatDateTime = (date) => {
  if (!date) {
    return '--'
  }
  return date.toLocaleString('zh-CN', { hour12: false })
}

const formatTraceValue = (value) => {
  const dict = {
    true: '是',
    false: '否',
    charge: '当前储能处于充电时段',
    discharge: '当前储能处于放电时段',
    idle: '当前储能处于空闲窗口',
    higher_or_equal: '峰价占优',
    higher: '峰价占优',
    lower_or_equal: '峰价不占优',
    lower: '峰价不占优',
    partial: '部分存储，部分自用',
    all: '全部存储',
    excess: '优先存储，其次自用，余电上网',
    pause_charge: '暂停储能充电',
    partial_cover: '优先存储，剩余自用'
  }
  return dict[value] || value
}

const traceSteps = computed(() => {
  const trace = dispatchSnapshot.value?.trace
  if (!Array.isArray(trace) || trace.length === 0) {
    return []
  }

  const steps = []
  const indexByKey = new Map()

  trace.forEach((entry) => {
    if (!entry) {
      return
    }
    const separatorIndex = entry.indexOf(':')
    const key = separatorIndex >= 0 ? entry.slice(0, separatorIndex) : entry
    const value = separatorIndex >= 0 ? entry.slice(separatorIndex + 1) : undefined

    if (!key) {
      return
    }

    let stepIndex = indexByKey.get(key)
    if (stepIndex === undefined) {
      const decisionNode = traceNodeMap.get(key)
      steps.push({
        key,
        title: decisionNode?.label || key,
        questionDescription: decisionNode?.description || '',
        resultLabel: undefined,
        resultDescription: undefined,
        rawValue: value
      })
      stepIndex = steps.length - 1
      indexByKey.set(key, stepIndex)
    }

    if (value !== undefined) {
      const branchNode = traceNodeMap.get(entry)
      const step = steps[stepIndex]
      step.rawValue = value
      step.resultLabel = branchNode?.label || formatTraceValue(value)
      step.resultDescription = branchNode?.description || ''
    }
  })

  return steps
})

const activeStepIndex = computed(() => (traceSteps.value.length ? traceSteps.value.length - 1 : 0))

const describeStep = (step) => {
  if (!step) {
    return ''
  }
  const segments = []
  if (step.resultLabel) {
    segments.push(`结果：${step.resultLabel}`)
  } else if (step.rawValue !== undefined) {
    segments.push(`结果：${formatTraceValue(step.rawValue)}`)
  }
  if (step.resultDescription) {
    segments.push(step.resultDescription)
  } else if (step.questionDescription) {
    segments.push(step.questionDescription)
  }
  return segments.join('，')
}

const contextData = computed(() => dispatchSnapshot.value?.context || {})

const evaluationMetrics = computed(() => {
  const ctx = contextData.value || {}
  return [
    { label: '负载功率 (kW)', value: formatNumber(ctx.load_power_kw) },
    { label: '光伏功率 (kW)', value: formatNumber(ctx.pv_power_kw) },
    { label: '储能可充功率 (kW)', value: formatNumber(ctx.storage_charge_cap_kw) },
    { label: '储能可放功率 (kW)', value: formatNumber(ctx.storage_discharge_cap_kw) }
  ]
})

const priceMetrics = computed(() => {
  const ctx = contextData.value || {}
  return [
    { label: '市电电价 (元/kWh)', value: formatNumber(ctx.grid_price_now) },
    { label: '光伏售电价 (元/kWh)', value: formatNumber(ctx.pv_feed_price) },
    { label: '储能已充电价 (元/kWh)', value: formatNumber(ctx.storage_charge_price), highlight: true }
  ]
})

const inputParameters = computed(() => {
  const ctx = contextData.value || {}
  return [
    { label: '当前窗口', value: windowLabels[ctx.storage_window] || ctx.storage_window || '--' },
    { label: '储能状态', value: stateLabels[ctx.storage_state] || ctx.storage_state || '--' },
    { label: '储能SOC (%)', value: formatNumber(ctx.storage_soc) },
    { label: '最高电价 (元/kWh)', value: formatNumber(ctx.grid_price_peak) },
    { label: '是否峰时', value: formatBoolean(ctx.is_peak_period) },
    { label: '关键负载 (kW)', value: formatNumber(ctx.critical_load_kw) }
  ]
})

const setDispatchSwitchValue = (value) => {
  suppressSwitchEvent = true
  dispatchEnabled.value = value
  nextTick(() => {
    suppressSwitchEvent = false
  })
}

const loadDispatchControl = async () => {
  try {
    const state = await dispatchAPI.getControl()
    if (state && typeof state.enabled === 'boolean') {
      setDispatchSwitchValue(state.enabled)
    }
  } catch (error) {
    ElMessage.error(handleApiError(error))
  }
}

const fetchData = async () => {
  loading.value = true
  try {
    const result = await dispatchAPI.getStatus()
    dispatchSnapshot.value = result
    if (typeof result?.enabled === 'boolean') {
      setDispatchSwitchValue(result.enabled)
    }
    await nextTick()
    await renderChart()
    lastUpdated.value = new Date()
  } catch (error) {
    ElMessage.error(handleApiError(error))
  } finally {
    loading.value = false
  }
}

const toggleDispatch = async (value) => {
  if (switchLoading.value) return
  switchLoading.value = true
  const previous = dispatchEnabled.value
  try {
    await dispatchAPI.setControl(value)
    ElMessage.success(value ? '调度已开启' : '调度已关闭')
    await fetchData()
  } catch (error) {
    setDispatchSwitchValue(previous)
    ElMessage.error(handleApiError(error))
  } finally {
    switchLoading.value = false
  }
}

const handleSwitchChange = (value) => {
  if (suppressSwitchEvent) {
    suppressSwitchEvent = false
    return
  }
  toggleDispatch(value)
}

onMounted(async () => {
  await loadDispatchControl()
  await fetchData()
})
onActivated(async () => {
  if (dispatchSnapshot.value) {
    await nextTick()
    await renderChart()
    if (chartInstance) {
      chartInstance.resize()
    }
  }
})
onBeforeUnmount(disposeChart)
</script>

<style scoped>
.dispatch-logic-page {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding-bottom: 20px;
}

.summary-card,
.info-card,
.actions-card,
.tree-card {
  border-radius: 14px;
  border: 1px solid #eef1f6;
  background: #ffffff;
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.card-title {
  font-weight: 600;
  font-size: 15px;
  color: #1f2937;
}

.card-subtitle {
  color: #8a93a6;
  font-size: 13px;
}

.card-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.update-time {
  font-size: 13px;
  color: #8a93a6;
}

.overview-row {
  width: 100%;
  margin-bottom: 8px;
  align-items: stretch;
}

.overview-row :deep(.el-col) {
  display: flex;
}

.summary-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
}

.summary-metrics {
  margin-top: 10px;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.metric-box {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  flex: 1;
}

.metric-box.metrics-compact {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.metric-card {
  background: linear-gradient(135deg, #f8fbff 0%, #edf3ff 100%);
  border-radius: 12px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 88px;
  border: 1px solid rgba(120, 144, 255, 0.08);
}

.metric-card.info {
  min-height: auto;
}

.metric-card.condensed {
  min-height: 72px;
  padding: 10px 12px;
}

.combined-overview {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.metrics-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.section-title {
  font-size: 13px;
  font-weight: 600;
  color: #4b5563;
  padding-left: 4px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  border-left: 3px solid #3b82f6;
  padding-left: 10px;
}

.metric-grid {
  display: grid;
  gap: 12px;
}

.metric-grid.three-columns {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.metric-grid.four-columns {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.metric-grid.six-columns {
  grid-template-columns: repeat(6, minmax(0, 1fr));
}

.metric-card.highlight {
  background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
  border: 1px solid rgba(251, 146, 60, 0.25);
  box-shadow: 0 4px 12px rgba(251, 146, 60, 0.1);
}

.metric-card.highlight .metric-label {
  color: #9a3412;
}

.metric-card.highlight .metric-value {
  color: #ea580c;
}

.metric-label {
  color: #6b7280;
  font-size: 12px;
  letter-spacing: 0.3px;
}

.metric-value {
  color: #111827;
  font-size: 20px;
  font-weight: 600;
}

.actions-card,
.info-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
}

.actions-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
  align-items: stretch;
  justify-content: center;
  padding-top: 4px;
}

.actions-content {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.actions-tags {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.action-chip {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 14px;
  border-radius: 14px;
  border: 1px solid rgba(147, 197, 253, 0.35);
  background: rgba(219, 234, 254, 0.6);
  transition: all 0.2s ease;
}

.action-chip:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(59, 130, 246, 0.18);
}

.action-chip.is-warning {
  border-color: rgba(250, 204, 21, 0.35);
  background: rgba(253, 230, 138, 0.55);
}

.action-chip.is-success {
  border-color: rgba(134, 239, 172, 0.35);
  background: rgba(187, 247, 208, 0.6);
}

.action-chip.is-info {
  border-color: rgba(191, 219, 254, 0.35);
  background: rgba(219, 234, 254, 0.6);
}

.action-chip.is-danger {
  border-color: rgba(252, 165, 165, 0.4);
  background: rgba(254, 226, 226, 0.6);
}

.chip-label {
  font-size: 14px;
  font-weight: 600;
  color: #1d4ed8;
}

.action-chip.is-warning .chip-label {
  color: #b45309;
}

.action-chip.is-success .chip-label {
  color: #047857;
}

.action-chip.is-info .chip-label {
  color: #1d4ed8;
}

.action-chip.is-danger .chip-label {
  color: #b91c1c;
}

.chip-desc {
  font-size: 12px;
  color: #4b5563;
}

.actions-note {
  font-size: 12px;
  color: #9ca3af;
}

.storage-command {
  font-size: 13px;
  color: #374151;
  background: rgba(96, 165, 250, 0.12);
  border: 1px solid rgba(37, 99, 235, 0.2);
  border-radius: 10px;
  padding: 8px 12px;
  margin-top: 6px;
}

.storage-command__value {
  font-weight: 600;
  color: #1d4ed8;
}

.actions-body :deep(.el-empty) {
  margin: auto;
}

.logic-section {
  width: 100%;
  margin-top: 6px;
}

.tree-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: linear-gradient(135deg, rgba(248, 250, 255, 0.95) 0%, rgba(238, 244, 255, 0.95) 100%);
  border-radius: 10px;
  padding: 12px 14px;
  border: 1px solid rgba(209, 223, 255, 0.6);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  gap: 18px;
}

.tree-header-main {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tree-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 16px;
  font-weight: 600;
  color: #1f2937;
}

.tree-title :deep(.el-icon) {
  color: #2563eb;
}

.title-text {
  letter-spacing: 0.5px;
}

.tree-badge {
  font-size: 12px;
  font-weight: 600;
  color: #1d4ed8;
  background: rgba(37, 99, 235, 0.12);
  border: 1px solid rgba(37, 99, 235, 0.28);
  border-radius: 999px;
  padding: 2px 10px;
}

.tree-caption {
  font-size: 13px;
  color: #4b5563;
}

.branch-hint {
  font-size: 13px;
  color: #6b7280;
  background: rgba(229, 231, 235, 0.5);
  border-radius: 8px;
  padding: 8px 12px;
  border: 1px dashed rgba(148, 163, 184, 0.5);
}

.branch-hint strong {
  color: #374151;
}

.logic-steps {
  padding: 4px 4px 12px;
}

.logic-steps :deep(.el-step__title) {
  font-size: 14px;
  color: #1f2937;
}

.logic-steps :deep(.el-step__description) {
  font-size: 13px;
  color: #4b5563;
}

.logic-chart-wrapper {
  min-height: 880px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px 0;
}

.logic-chart {
  width: 100%;
  height: 820px;
}

.chart-empty {
  width: 100%;
}

@media (max-width: 1024px) {
  .metric-box {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .metric-box.metrics-compact {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .metric-grid.three-columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .metric-grid.four-columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .metric-grid.six-columns {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .metric-value {
    font-size: 18px;
  }
}

@media (max-width: 768px) {
  .dispatch-logic-page {
    gap: 16px;
  }
  .tree-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  .metric-box {
    grid-template-columns: repeat(1, minmax(0, 1fr));
  }
  .metric-box.metrics-compact {
    grid-template-columns: repeat(1, minmax(0, 1fr));
  }
  .metric-grid.three-columns,
  .metric-grid.four-columns,
  .metric-grid.six-columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .logic-chart-wrapper {
    min-height: 680px;
    padding: 12px 0;
  }
  .logic-chart {
    height: 680px;
  }
}
</style>
